/**
 * 
 */
package com.massyframework.beanskin.launcher.scanning.maven;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.massyframework.beanskin.framework.Framework;
import com.massyframework.beanskin.launcher.scanning.ModuleResources;
import com.massyframework.beanskin.util.exceptioncollect.ExceptionCollectUtils;



/**
 * {@link AbstractMavenProjectRepository}, 实现{@link MavenProjectRepository}的基本语义。
 * <pre>
 * 构建Maven项目仓储的业务逻辑:
 * 首先，通过classPath按maven工程的结构，定位到对应的pom.xml文件。
 * 其次，遍历加载整个pom文件的工程族，形成MavenProject仓储。
 * </pre>
 * @author huangkh
 *
 */
abstract class AbstractMavenProjectRepository implements MavenProjectRepository {
	
	private static final String PROTOCAL = "file";
	protected Logger logger = LoggerFactory.getLogger(MavenProjectRepository.class);
	
	private Map<Coordinate, MavenProject> projectMap;
	private Map<File, MavenProject> pomMap;

	/**
	 * 创建新实例
	 */
	protected AbstractMavenProjectRepository() {
		this.projectMap = new HashMap<>();
		this.pomMap = new HashMap<>();
	}
	
	/**
	 * 执行初始化
	 * @param resolver {@link DevelopingMavenProjectResolver}
	 */
	protected void doInit(DevelopingMavenProjectResolver resolver ) {
		if (this.pomMap.isEmpty()) {
			List<File> pomFiles = this.locateAllPomXml();
			for (File pomFile: pomFiles) {
				//加载项目
				this.loadDevelopingMavenProject(pomFile, resolver);
			}
		}
	}
		
	@Override
	public MavenProject findMavenProject(ModuleResources resources) {
		return this.pomMap.get(resources.getPomFile());
	}
	
	/**
	 * 根据坐标查找maven项目，如果未找到，则根据{@code autoLoad}决定是否在本地仓储中加载LocaMavenProject.
	 * @param coordinate {@link Coordinate},坐标
	 * @param autoLoad {@link boolean},是否自动加载本地的LocalMavenProject
	 * @return {@link MavenProject}
	 */
	protected MavenProject findMavenProject(Coordinate coordinate, boolean autoLoad) {
		if (autoLoad) {
			MavenProject result =
					this.projectMap.get(coordinate);
			if (result == null) {
				result = this.findDeploveingMavenProject(coordinate);
					if (result == null) {
					try {
						result = findLocalMavenProject(coordinate);
						this.projectMap.put(coordinate, result);
					} catch (MalformedURLException e) {
						if (logger.isErrorEnabled()) {
							logger.error("cannot load maven project:" + coordinate+".", e);
						}
						ExceptionCollectUtils.addException(e);
					}
					if (result != null) {
						this.projectMap.put(coordinate, result);
					}
				}
			}
			return result;
		}else {
			return this.projectMap.get(coordinate);
		}					
	}
	
	/**
	 * 在本地仓库中查找和加载LocalMavenProject
	 * @param coordinate {@link Coordinate},maven工程坐标
	 * @return {@link LocalMavenProject}
	 * @throws MalformedURLException
	 */
	protected LocalMavenProject findLocalMavenProject(Coordinate coordinate) 
			throws MalformedURLException {
		StringBuilder builder = new StringBuilder();
		builder.append(File.separator)
			.append(coordinate.getGroupId().replace(".", File.separator))
			.append(File.separator)
			.append(coordinate.getArtifactId())
			.append(File.separator)
			.append(coordinate.getVersion())
			.append(File.separator)
			.append(coordinate.getArtifactId())
			.append("-")
			.append(coordinate.getVersion())
			.append(".jar");
		File repo = MavenSettings.getInstance().getLocalRepository().toFile();
		File jarFile =new File(repo, builder.toString());
		if (jarFile.exists()) {
			return LocalMavenProject.newBuilder()
				.coordinate(coordinate)
				.jarURL(jarFile.toURI().toURL())
				.build();
		}
		
		return null;
	}
	
	/**
	 * 添加开发中的maven项目
	 * @param pomFile {@link File},项目的pomFile文件
	 * @param project {@link DevelopingMavenProject},maven项目
	 */
	protected void addDevelopingMavenProject(File pomFile, 
			DevelopingMavenProject project) {
		this.projectMap.putIfAbsent(project.getCoordinate(), project);
		this.pomMap.putIfAbsent(pomFile, project);
	}
	 
	/**
	 * 判断是否包含特定的maven工程
	 * @param coordinate {@link Coordinate},工程坐标
	 * @return {@link boolean}
	 */
	protected boolean containsMavenProject(Coordinate coordinate) {
		if (coordinate == null) return false;
		return this.projectMap.containsKey(coordinate);
	}
	
	/**
	 * 判断是否包含特定的maven工程
	 * @param pomFile {@link File},工程的pom文件
	 * @return {@link boolean}
	 */
	protected boolean containsMavenProject(File pomFile) {
		if (pomFile == null) return false;
		return this.pomMap.containsKey(pomFile);
	}
	
	/**
	 * 加载Maven项目
	 * @param pomFile
	 * @param resolver
	 */
	protected void loadDevelopingMavenProject(File pomFile,
			DevelopingMavenProjectResolver resolver) {
		if (this.containsMavenProject(pomFile)) {
			return;
		}
		
		try {
			DevelopingMavenProject project = resolver.parse(pomFile);
			if (project != null) {
				this.addDevelopingMavenProject(pomFile, project);
				
				//迭代子模块
				//this.recursiveModules(project, resolver);
				
				//加载父工程
				/*
				ParentCoordinate parent = project.getParentCoordinate();
				if (parent != null) {
					if (!this.containsMavenProject(parent)) {
						File parentPomFile = this.locateParentPomXml(parent, project.getBaseDir());
						if (parentPomFile != null) {
							this.loadDevelopingMavenProject(parentPomFile, resolver);
						}
					}
				}
				*/
			}
		}catch(Exception e) {
			if (logger.isErrorEnabled()) {
				logger.error("parse maven project failed: ".concat(pomFile.getAbsolutePath()), e);
			}
		}
	}
	
	/**
	 * 递归加载子模块
	 * @param parent {@link DevelopingMavenProject},父工程
	 * @param resolver {@link DevelopingMavenProjectResolver}, 开发maven项目解析器
	 */
	public void recursiveModules(DevelopingMavenProject parent,
			DevelopingMavenProjectResolver resolver) {
		List<String> moduleNames = parent.getModuleNames();
		for (String moduleName: moduleNames) {
			File pomFile = this.locateModulePomXml(moduleName, parent.getBaseDir());
						
			if (pomFile != null) {
				if (!this.containsMavenProject(pomFile)) {
					try {
						DevelopingMavenProject module = resolver.parse(pomFile);
						if (module != null) {
							this.addDevelopingMavenProject(pomFile, module);
							this.recursiveModules(module, resolver);
						}
					}catch(Exception e) {
						if (logger.isErrorEnabled()) {
							logger.error("resolver maven project failed: " + pomFile.getAbsolutePath() + ".", e);
						}
						ExceptionCollectUtils.addException(e);
					}
				}
			}
		}
	}
	
	/**
	 * 定位父工程的pom.xml文件
	 * @param coordinate {@link ParentCoordinate},父工程的坐标
	 * @param baseDir {@link File},子项目的基本目录
	 * @return {@link File}，可能返回null.
	 */
	protected File locateParentPomXml(ParentCoordinate coordinate, File baseDir){
        try {
        	String relativePath = coordinate.getRelativePath();
        	if (relativePath.startsWith("./") || relativePath.startsWith("../")) {
        		URI newURI = baseDir.toURI().resolve(relativePath);
        		return Paths.get(newURI).toFile();
        	}
        	File result = new File(relativePath);
        	if (!result.exists()) {
				if (logger.isWarnEnabled()) {
					logger.error("cannot found pom.xml: " + result.getAbsolutePath());
				}
				return null;
        	}
        	return result;
        			
        }catch(Exception e){
            if (logger.isErrorEnabled()){
                logger.error("resolve parent File failed: "
                		.concat(coordinate.toString()).concat("."), e);
            }
            return null;
        }
    }
	
	/**
	 * 定位子模块的pom.xml文件
	 * @param moduleName {@link String},模块名称
	 * @param baseDir {@link File}， 工程的基本目录
	 * @return {@link File}，可能返回null.
	 */
	private File locateModulePomXml(String moduleName, File baseDir) {
		File dir = new File(baseDir, moduleName);
		File result = new File(dir, "pom.xml");
		if (!result.exists()) {
			if (logger.isWarnEnabled()) {
				logger.error("cannot found pom.xml: " + result.getAbsolutePath());
			}
			return null;
		}
		return result;
	}
	
	/**
	 * 定位所有的pom.xml文件
	 * @param xmlFiles {@link List}, plugin.xml或者assembly.xml文件
	 * @return {@link List}
	 */
	protected List<File> locateAllPomXml(){
		List<File> result = new ArrayList<>();
		
		List<String> moduleDirs = this.locateModuleDir();
		for (String moduleDir: moduleDirs) {
			File dir = new File(moduleDir);
			File pomFile = this.locatePomXml(dir);
			if (pomFile == null) {
				if (logger.isWarnEnabled()) {
					logger.warn("cannot found pom.xml with: " + moduleDir + ".");
				}
			}else {
				result.add(pomFile);
			}

		}
		return result;
	}

	/**
	 * 根据dir目录，定位到maven的pom文件。
	 * @param dir {@link File}, MODULE目录
	 * @return {@link File}
	 */
	protected File locatePomXml(File dir) {
		File parent = dir;
		File pomFile = null;
		do {
			parent = parent.getParentFile();
			if (parent == null) return null;
			
			pomFile = new File(parent, POM);
		}while (!isPomXmlWithMaven(pomFile));
		
		return pomFile;
	}
	
	/**
	 * 判断文件是否为Maven项目pom.xml文件
	 * <p>
	 * 同目录下，存在target文件夹。
	 * @param pomFile  {@link File}
	 * @return {@link boolean}
	 */
	protected boolean isPomXmlWithMaven(File pomFile) {
		if (pomFile.exists()) {
			File target = new File(pomFile.getParent(), "target");
			return target.exists() && target.isDirectory();
		}
		return false;
	}

	/**
	 * 定位MODULE/目录
	 * @return {@link List}
	 */
	protected List<String> locateModuleDir() {
		List<String> result = new ArrayList<>();
		ClassLoader classLoader = Framework.class.getClassLoader();
		try {
			
			Enumeration<URL> em = classLoader.getResources("MODULE/");
			while (em.hasMoreElements()) {
				URL url = em.nextElement();
				if (PROTOCAL.equals(url.getProtocol())){
					String file = url.getFile();
					result.add(file);
				}
			}			
			
		} catch (IOException e) {
			
		}
		
		return result;
	}
	
	/**
	 * 查找本地的开发项目
	 * @param coordinate {@link Coordinate}
	 * @return {@link MavenProject},可能返回null.
	 */
	protected abstract MavenProject findDeploveingMavenProject(Coordinate coordinate);
}
